﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using Thor.Domain;
using Thor.Exchange.WebServices.ExchangeInterface;

namespace Thor.Exchange.WebServices
{
    /// <summary>
    /// Provides access to calendar data via Exchange Web Services.
    /// </summary>
    public class CalendarProvider : CalendarProviderBase
    {
        /// <summary>
        /// Authenticates the specified credentials against the calendar provider.
        /// </summary>
        /// <param name="credential"></param>
        /// <param name="serviceUrl">Url to the service providing calendar data.</param>
        /// <returns></returns>
        public override AuthenticationResult Authenticate(NetworkCredential credential, string serviceUrl)
        {
            //To authenticate we're currently just trying to make a call to Exchange using
            //the provider credentials. If autentication fails on Exchange an Exception is
            //raised which we trap and include in the result.

            //We should probably revisit this at some point as there must be a better
            //way to do it.

            AuthenticationResult result = new AuthenticationResult();

            try
            {
                ResolveNamesType resolveNamesType = new ResolveNamesType();
                resolveNamesType.ReturnFullContactData = true;
                resolveNamesType.UnresolvedEntry = credential.UserName;

                ExchangeServiceBinding binding = GetService(credential, serviceUrl);
                ResolveNamesResponseType resolveNamesResponse = binding.ResolveNames(resolveNamesType);

                result.Success = true;
                result.Message = "Authenticated.";
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.Message = ex.Message;
            }

            return result;
        }

        /// <summary>
        /// Gets data for a specific calendar.
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        protected override Calendar OnGetCalendar(CalendarRequest request)
        {
            Calendar calendar = new Calendar();
            calendar.Appointments = new List<Appointment>();

            using (ExchangeServiceBinding binding = GetService(request.Credential, request.ServiceUrl))
            {
                calendar.Appointments.AddRange(GetAppointments(request.CalendarId, binding, request.StartDate.Value, request.EndDate.Value));
            }

            return calendar;
        }

        private List<Appointment> GetAppointments(string source, ExchangeServiceBinding binding, DateTime startDate, DateTime endDate)
        {
            FindItemType findItemRequest = new FindItemType
            {
                ItemShape =
                    new ItemResponseShapeType { BaseShape = DefaultShapeNamesType.AllProperties }
            };

            findItemRequest.Item = new CalendarViewType
            {
                StartDate = startDate,
                EndDate = endDate
            };

            // FindItem also takes a traversal value - Shallow is fine
            findItemRequest.Traversal = ItemQueryTraversalType.Shallow;

            DistinguishedFolderIdType otherUsersCalendarFolder = new DistinguishedFolderIdType { Id = DistinguishedFolderIdNameType.calendar };
            EmailAddressType email = new EmailAddressType();
            email.EmailAddress = source;
            otherUsersCalendarFolder.Mailbox = email;
            findItemRequest.ParentFolderIds = new BaseFolderIdType[] { otherUsersCalendarFolder };

            // Finally submit the FindItem request to the server
            FindItemResponseType response = binding.FindItem(findItemRequest);

            //If everything went well, you get a set of mesages as response
            List<Appointment> returnlist = new List<Appointment>();

            foreach (FindItemResponseMessageType firmt in response.ResponseMessages.Items)
            {
                if (firmt.RootFolder != null && firmt.RootFolder.TotalItemsInView > 0)
                {
                    foreach (CalendarItemType it in ((ArrayOfRealItemsType)firmt.RootFolder.Item).Items)
                    {
                        returnlist.Add(new Appointment()
                        {
                            Subject = it.Subject,
                            StartTime = it.Start,
                            EndTime = it.End,
                            Id = Guid.NewGuid()
                        });
                    }
                }
            }

            return returnlist;
        }

        /// <summary>
        /// Books an appointment.
        /// </summary>
        ///<param name="appointmentRequest"></param>
        protected override AppointmentResponse OnBookDirect(AppointmentRequest appointmentRequest)
        {
            AppointmentResponse result = new AppointmentResponse();

            try
            {
                if (!appointmentRequest.IgnoreBusyData)
                {
                    if (this.IsBusy(appointmentRequest))
                    {
                        result.Success = false;
                        result.Message = string.Format("The calender for {0} is booked during the requested time.", appointmentRequest.CalendarId);
                        return result;
                    }
                }

                CalendarItemType appointment = new CalendarItemType();

                appointment.ItemClass = "IPM.Appointment";

                appointment.Subject = appointmentRequest.Subject;

                appointment.Start = appointmentRequest.StartTime;
                appointment.StartSpecified = true;

                appointment.End = appointmentRequest.EndTime;
                appointment.EndSpecified = true;

                appointment.RequiredAttendees = new AttendeeType[1];
                AttendeeType attendee = new AttendeeType() { Mailbox = new EmailAddressType() { EmailAddress = appointmentRequest.CalendarId } };
                appointment.RequiredAttendees[0] = attendee;

                DistinguishedFolderIdType folder = new DistinguishedFolderIdType();
                folder.Id = DistinguishedFolderIdNameType.calendar;

                NonEmptyArrayOfAllItemsType arrayOfItems = new NonEmptyArrayOfAllItemsType();
                arrayOfItems.Items = new ItemType[1];

                arrayOfItems.Items[0] = appointment;

                CreateItemType createItemRequest = new CreateItemType();

                createItemRequest.SendMeetingInvitations = CalendarItemCreateOrDeleteOperationType.SendOnlyToAll;
                createItemRequest.SendMeetingInvitationsSpecified = true;

                createItemRequest.SavedItemFolderId = new TargetFolderIdType();
                createItemRequest.SavedItemFolderId.Item = folder;

                createItemRequest.Items = arrayOfItems;

                ExchangeServiceBinding binding = GetService(appointmentRequest.Credential, appointmentRequest.ServiceUrl);

                CreateItemResponseType createItemResponse = binding.CreateItem(createItemRequest);

                result.Success = true;
                result.Message = "The appointment has been booked.";
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.Message = ex.Message;
            }

            return result;
        }

        /// <summary>
        /// Books and appointment via a booking agent.
        /// </summary>
        /// <param name="appointmentRequest"></param>
        protected override AppointmentResponse OnBookViaAgent(AppointmentRequest appointmentRequest)
        {
            AppointmentResponse result = new AppointmentResponse();

            try
            {
                CreateItemType createItemRequest = new CreateItemType();

                createItemRequest.MessageDisposition = MessageDispositionType.SendAndSaveCopy;
                createItemRequest.MessageDispositionSpecified = true;

                createItemRequest.SavedItemFolderId = new TargetFolderIdType();
                DistinguishedFolderIdType sentitems = new DistinguishedFolderIdType();
                sentitems.Id = DistinguishedFolderIdNameType.sentitems;
                createItemRequest.SavedItemFolderId.Item = sentitems;

                createItemRequest.Items = new NonEmptyArrayOfAllItemsType();

                MessageType message = new MessageType();
                message.Subject = "Meeting Request";
                message.Body = new BodyType();
                message.Body.BodyType1 = BodyTypeType.Text;
                message.Body.Value = this.GetBodyFromRequest(appointmentRequest);
                message.ItemClass = "IPM.Note";
                message.Sender = new SingleRecipientType();
                message.Sender.Item = new EmailAddressType();
                message.Sender.Item.EmailAddress = appointmentRequest.BookingAgentEmail;
                message.ToRecipients = new EmailAddressType[1];
                message.ToRecipients[0] = new EmailAddressType();
                message.ToRecipients[0].EmailAddress = appointmentRequest.BookingAgentEmail;
                message.Sensitivity = SensitivityChoicesType.Normal;

                createItemRequest.Items.Items = new ItemType[1];
                createItemRequest.Items.Items[0] = message;

                ExchangeServiceBinding binding = GetService(appointmentRequest.Credential, appointmentRequest.ServiceUrl);
                CreateItemResponseType createItemResponse = binding.CreateItem(createItemRequest);

                result.Success = true;
                result.Message = string.Format("The appointment request for {0} has been sent to the booking agent ({1}).", appointmentRequest.CalendarId, appointmentRequest.BookingAgentEmail);
            }
            catch (Exception ex)
            {
                result.Success = false;
                result.Message = ex.Message;
            }

            return result;
        }

        private ExchangeServiceBinding GetService(NetworkCredential credential, string serviceUrl)
        {
            
            //TODO: deal with Exchange 2010?
            return new ExchangeInterface.ExchangeServiceBinding { Url = serviceUrl, Credentials = credential };
        }
    }
}
